<!--
 * @Author: Dorange.Wang
 * @Date: 2021-04-07 16:51:16
 * @LastEditors: wangming
 * @LastEditTime: 2022-07-30 15:13:56
 * @Description: file content
-->
<template>
  <div
    class="custom-tree-select"
    :class="customClass"
    :style="$options.style || defaultStyle"
  >
    <template v-if="readOnly">
      <template v-if="multiple">
        <el-tag
          v-for="item in listDataSelected"
          :key="item[fieldOptions.value]"
          type="info"
          >{{ item[fieldOptions.label] }}</el-tag
        >
      </template>
      <span v-else-if="listDataSelected.length > 0">{{
        listDataSelected[0][fieldOptions.label]
      }}</span>
    </template>
    <el-popover
      v-else
      :disabled="disabled"
      v-model="popoverVisable"
      :width="popoverWidth"
      placement="bottom-start"
      trigger="click"
      popper-class="custom-tree-select__popover"
      @show="show"
      @hide="filterText = ''"
    >
      <el-input
        :validate-event="false"
        v-if="showFilter"
        v-model="filterText"
        class="mb10"
        clearable
        :maxlength="64"
        prefix-icon="el-icon-search"
      />
      <el-tree
        ref="tree"
        :data="treeData"
        default-expand-all
        :expand-on-click-node="false"
        @node-click="handleTreeNodeClick"
        :filter-node-method="filterNodeMethod"
      >
        <span
          class="ks-col ell"
          :title="data[fieldOptions.label]"
          :class="{
            'custom-tree-select__is-disabled':
              data[fieldOptions.disabled] === disabledValue,
            active: multiple
              ? value.indexOf(data[fieldOptions.value] + '') > -1 ||
                value.indexOf(data[fieldOptions.value]) > -1
              : value == data[fieldOptions.value],
          }"
          slot-scope="{ data }"
        >
          <slot name="node" :data="data">
            {{ data[fieldOptions.label] }}
          </slot>
        </span>
      </el-tree>

      <el-select
        :disabled="disabled"
        slot="reference"
        :style="{
          width: '100%',
        }"
        class="custom-tree-select__input"
        popper-class="custom-tree-select__options"
        :value="value"
        :multiple="multiple"
        @change="handleValueChange"
        :clearable="clearable"
        :placeholder="placeholder"
      >
        <el-option
          v-for="item in listDataSelected"
          :key="
            valueString
              ? String(item[fieldOptions.value])
              : item[fieldOptions.value]
          "
          :value="
            valueString
              ? String(item[fieldOptions.value])
              : item[fieldOptions.value]
          "
          :label="item[fieldOptions.label]"
        >
          <slot name="node" :data="item">
            {{ item[fieldOptions.label] }}
          </slot>
        </el-option>
      </el-select>
    </el-popover>
  </div>
</template>

<script>
import { treeToArray, randomString } from "@/utils/util";

export default {
  name: "TreeSelect",

  model: {
    prop: "value",
    event: "change",
  },

  props: {
    value: {
      type: [Number, String, Array],
      default: undefined,
    },
    multiple: {
      // 多选
      type: Boolean,
      default: false,
    },
    clearable: {
      // 清空
      type: Boolean,
      default: false,
    },
    readOnly: {
      // 只读 单选渲染成文本 多选渲染为tag
      type: Boolean,
      default: false,
    },
    disabled: {
      // 禁用
      type: Boolean,
      default: false,
    },
    placeholder: {
      type: String,
      default: undefined,
    },
    treeData: {
      type: Array,
      default: () => [],
    },
    fieldMap: {
      // 字段映射
      type: Object,
      default: () => {},
    },
    filterable: {
      // 是否支持搜索
      type: Boolean,
      default: false,
    },
    filterNodeMethod: {
      // 自定义搜索函数
      type: Function,
      default: (value, data) => {
        if (!value) return true;
        return data.name.indexOf(value) !== -1;
      },
    },
    valueString: {
      // 强制将值转化为字符串
      type: Boolean,
      default: false,
    },
    disabledValue: {
      // 禁用选项的判断值 默认true
      type: [Boolean, String],
      default: true,
    },
  },
  data() {
    return {
      defaultStyle: {
        width: "100%",
      },
      customClass: "customClass_",
      popoverWidth: 300,
      popoverVisable: false,
      filterText: "",
    };
  },

  computed: {
    showFilter() {
      return this.filterable;
    },
    fieldOptions() {
      return Object.assign(
        {
          value: "id", // value 对应的字段
          label: "name", // label 对应的字段 同时支持作用域插槽
          disabled: "disabled",
        },
        this.fieldMap
      );
    },
    listData() {
      // tree转list
      return treeToArray(this.treeData);
    },
    listDataSelected() {
      // 已选中的数据对象
      const { value, multiple, listData, fieldOptions } = this;
      const key = fieldOptions.value;
      if (!value) {
        return [];
      } else if (multiple) {
        const values = value.map(String);
        return listData.filter((item) => values.includes(String(item[key])));
      } else {
        return listData.filter((item) => {
          return String(item[key]) === String(value);
        });
      }
    },
  },

  watch: {
    filterText(val) {
      this.$refs.tree.filter(val);
    },
  },

  created() {
    this.customClass = `customClass_${randomString(8)}`;
  },
  methods: {
    show() {
      this.popoverWidth = document
        .querySelector(`.${this.customClass}`)
        .getBoundingClientRect().width;
    },
    handleTreeNodeClick(item) {
      const { value: _value, disabled: _disabled } = this.fieldOptions;
      if (item[_disabled] === this.disabledValue) {
        return;
      }
      const { multiple } = this;
      const curVal = this.valueString ? String(item[_value]) : item[_value];
      let result;
      if (multiple) {
        const value = this.valueString
          ? (this.value || []).map((item) => String(item))
          : this.value;
        const isIn = (value || []).includes(curVal);
        result = isIn
          ? value.filter((e) => curVal !== e)
          : (value || []).concat(curVal);
        result = result.length === 0 ? undefined : result;
      } else {
        result = curVal;
        this.popoverVisable = false;
      }
      this.$emit("change", result);
    },

    // handleSelectRemoveTag (tag) {
    //   console.log(tag)
    //   const { value } = this
    //   let result = value.filter(e => tag !== e)
    //   result = result.length === 0 ? undefined : result
    //   this.$emit('change', result)
    // },

    handleValueChange(value) {
      this.$emit("change", value);
    },
  },
};
</script>

<style lang="scss" scoped>
.active {
  color: #409eff;
  font-weight: 500;
}
</style>
<style  lang="scss">
.custom-tree-select {
  &__options {
    display: none !important;
  }

  &__popover {
    box-sizing: border-box;
    max-height: 274px;
    overflow: auto;
  }

  &__is-disabled {
    color: #ccc;
  }

  > span:not(.el-tag),
  .el-popover__reference-wrapper {
    width: 100%;
    display: block;
  }
}
</style>
